package com.tianji.learning.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.tianji.common.utils.CollUtils;
import com.tianji.common.utils.DateUtils;
import com.tianji.common.utils.UserContext;
import com.tianji.learning.constants.RedisConstants;
import com.tianji.learning.domain.po.PointsBoardSeason;
import com.tianji.learning.domain.po.PointsRecord;
import com.tianji.learning.domain.vo.PointsStatisticsVO;
import com.tianji.learning.enums.PointsRecordType;
import com.tianji.learning.mapper.PointsBoardSeasonMapper;
import com.tianji.learning.mapper.PointsRecordMapper;
import com.tianji.learning.service.IPointsBoardSeasonService;
import com.tianji.learning.service.IPointsRecordService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

import static com.tianji.learning.constants.RedisConstants.REDIS_EXPIRE_TIME_DAYS;

/**
 * <p>
 * 学习积分记录，每个月底清零 服务实现类
 * </p>
 *
 * @author huanmo
 * @since 2025-09-24
 */
@Service
@Slf4j
@RequiredArgsConstructor
public class PointsRecordServiceImpl extends ServiceImpl<PointsRecordMapper, PointsRecord> implements IPointsRecordService {

    private final StringRedisTemplate redisTemplate;

    private final IPointsBoardSeasonService pointsBoardSeasonService;

    @Override
    public void addPointsRecord(Long userId, int points, PointsRecordType type) {
        LocalDateTime now = LocalDateTime.now();
        int maxPoints = type.getMaxPoints();
        // 1.判断当前方式有没有积分上限
        int realPoints = points;
        if (maxPoints > 0) {
            // 2.有，则需要判断是否超过上限
            LocalDateTime begin = DateUtils.getDayStartTime(now);
            LocalDateTime end = DateUtils.getDayEndTime(now);
            // 2.1.查询今日已得积分
            int currentPoints = queryUserPointsByTypeAndDate(userId, type, begin, end);
            // 2.2.判断是否超过上限
            if (currentPoints >= maxPoints) {
                // 2.3.超过，直接结束
                log.info("用户今日积分已达上限，不再添加积分");
                return;
            }
            // 2.4.没超过，保存积分记录
            if (currentPoints + points > maxPoints) {
                // 保存时，只能保存不超上限的那一部分
                realPoints = maxPoints - currentPoints;
                log.info("积分超过上限，实际添加积分: {}", realPoints);
            }
        }
        // 3.没有，直接保存积分记录
        PointsRecord p = new PointsRecord();
        p.setPoints(realPoints);
        p.setUserId(userId);
        p.setType(type);
        save(p);
        // 4.更新总积分到Redis
        String key = RedisConstants.POINTS_BOARD_KEY_PREFIX + now.format(DateUtils.POINTS_BOARD_SUFFIX_FORMATTER);
        redisTemplate.opsForZSet().incrementScore(key, userId.toString(), realPoints);
        log.info("积分记录保存成功，用户ID: {}, 积分: {}", userId, realPoints);
    }

    @Override
    public List<PointsStatisticsVO> queryMyPointsToday() {
        // 1.获取用户
        Long userId = UserContext.getUser();
        // 2.获取日期
        LocalDateTime now = LocalDateTime.now();
        LocalDateTime begin = DateUtils.getDayStartTime(now);
        LocalDateTime end = DateUtils.getDayEndTime(now);
        // 3.构建查询条件
        QueryWrapper<PointsRecord> wrapper = new QueryWrapper<>();
        wrapper.lambda()
                .eq(PointsRecord::getUserId, userId)
                .between(PointsRecord::getCreateTime, begin, end);
        // 4.查询
        List<PointsRecord> list = getBaseMapper().queryUserPointsByDate(wrapper);
        if (CollUtils.isEmpty(list)) {
            return CollUtils.emptyList();
        }
        // 5.封装返回
        List<PointsStatisticsVO> vos = new ArrayList<>(list.size());
        for (PointsRecord p : list) {
            PointsStatisticsVO vo = new PointsStatisticsVO();
            vo.setType(p.getType().getDesc());
            vo.setMaxPoints(p.getType().getMaxPoints());
            vo.setPoints(p.getPoints());
            vos.add(vo);
        }
        return vos;
    }

    /**
     * 查询指定赛季的数据
     */
    @Override
    public List<PointsRecord> selectBySeason(Integer season) {
        // 1.获取赛季时间范围
        PointsBoardSeason pointsBoardSeason = pointsBoardSeasonService.queryBySeason(season);
        if (pointsBoardSeason == null) {
            log.warn("赛季{}不存在", season);
            return List.of();
        }
        LocalDateTime begin = pointsBoardSeason.getBeginTime().atStartOfDay();
        LocalDateTime end = pointsBoardSeason.getEndTime().atTime(LocalTime.MAX);
        log.debug("要查询的数据属于第{}赛季，该赛季的起始时间为{}，{}", season, begin, end);
        // 2.查询数据
        return lambdaQuery()
                .between(PointsRecord::getCreateTime, begin, end)
                .list();
    }

    /**
     * 分片广播任务 -- 查询指定赛季的数据
     */
    @Override
    public List<PointsRecord> selectBySeasonWithShard(Integer season, int shardIndex, int shardTotal) {
        PointsBoardSeason pointsBoardSeason = pointsBoardSeasonService.queryBySeason(season);
        if (pointsBoardSeason == null) {
            log.warn("赛季{}不存在", season);
            return List.of();
        }
        LocalDateTime begin = pointsBoardSeason.getBeginTime().atStartOfDay();
        LocalDateTime end = pointsBoardSeason.getEndTime().atTime(LocalTime.MAX);
        // 使用MOD分片
        return lambdaQuery()
                .ge(PointsRecord::getCreateTime, begin)
                .le(PointsRecord::getCreateTime, end)
                .apply("MOD(id, {0}) = {1}", shardTotal, shardIndex)
                .list();
    }

    /**
     * 删除指定赛季的数据
     */
    @Override
    public void deleteBySeason(Integer season) {
        // 1.获取赛季时间范围
        PointsBoardSeason pointsBoardSeason = pointsBoardSeasonService.queryBySeason(season);
        if (pointsBoardSeason == null) {
            log.warn("赛季{}不存在", season);
            return;
        }
        LocalDateTime begin = pointsBoardSeason.getBeginTime().atStartOfDay();
        LocalDateTime end = pointsBoardSeason.getEndTime().atTime(LocalTime.MAX);
        // 2.分批删除，避免一次性删除大量数据
        boolean hasMore = true;
        // 每批删除1000条
        int batchSize = 1000;
        int totalDeleted = 0;
        while (hasMore) {
            int deletedCount = getBaseMapper().deleteBySeasonBatch(begin, end, batchSize);
            totalDeleted += deletedCount;
            // 如果删除数量等于批次大小，可能还有更多数据
            hasMore = deletedCount == batchSize;
            if (hasMore) {
                try {
                    // 短暂休息，避免对数据库造成太大压力
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    break;
                }
            }
        }
        log.info("赛季{}数据删除完成，共删除{}条记录", season, totalDeleted);
    }

    /**
     * 批量插入数据到指定表
     * 分批写入
     */
    @Override
    public boolean batchInsertToTargetTable(String targetTableName, List<PointsRecord> pointsRecordList) {
        if (CollUtils.isEmpty(pointsRecordList)) {
            return true;
        }
        try {
            // 分批写入，每批1000条
            int batchSize = 5;
            int total = pointsRecordList.size();
            int successCount = 0;
            for (int i = 0; i < total; i += batchSize) {
                int end = Math.min(i + batchSize, total);
                List<PointsRecord> batch = pointsRecordList.subList(i, end);
                int result = baseMapper.batchInsertToTable(targetTableName, batch);
                if (result > 0) {
                    successCount += batch.size();
                }
                log.debug("已写入{}/{}条记录", successCount, total);

                // 批次间短暂休眠，减轻数据库压力
                if (end < total) {
                    Thread.sleep(50);
                }
            }
            log.info("表{}写入完成，成功{}/{}条", targetTableName, successCount, total);
            return successCount == total;
        } catch (Exception e) {
            log.error("批量插入到表{}失败", targetTableName, e);
            return false;
        }
    }

    private int queryUserPointsByTypeAndDate(
            Long userId, PointsRecordType type, LocalDateTime begin, LocalDateTime end) {
        // 1.查询条件
        QueryWrapper<PointsRecord> wrapper = new QueryWrapper<>();
        wrapper.lambda()
                .eq(PointsRecord::getUserId, userId)
                .eq(type != null, PointsRecord::getType, type)
                .between(begin != null && end != null, PointsRecord::getCreateTime, begin, end);
        // 2.调用mapper，查询结果
        Integer points = getBaseMapper().queryUserPointsByTypeAndDate(wrapper);
        // 3.判断并返回
        return points == null ? 0 : points;
    }
}